//
//  DYDownload.m
//  DYDownload
//
//  Created by 吴新庭 on 2019/5/9.
//  Copyright © 2019 iyinyue. All rights reserved.
//

#import "DYDownload.h"
#import "DYDownloadSchedule.h"
#import "SSZipArchive.h"
#import "DYDownloadVersion.h"
#import "NSString+Version.h"

#pragma mark - TargetAction
@interface DYDownloadEventAction : NSObject
@property (nonatomic, assign) DYDownloadEvents event;
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL sel;

- (BOOL)invoke:(NSObject *)obj;
@end

@implementation DYDownloadEventAction

- (BOOL)invoke:(NSObject *)obj {
    if (!self.target) {
        // 如果target已经被释放，直接返回继续
        return YES;
    }
    
    IMP imp = [self.target methodForSelector:self.sel];
    BOOL (*should)(id, SEL, NSObject *) = (void *)imp;
    BOOL result = should(self.target, self.sel, obj);
    
    NSMethodSignature *methodSig = [self.target methodSignatureForSelector:self.sel];
    if (*(methodSig.methodReturnType) != 'B') {
        result = YES;
    }
    return result;
}

@end

#pragma mark - DYDownload
static NSString * const kDefaultDirectory = @"DYDownload-default";
@interface DYDownload ()

@property (nonatomic, strong) NSURL *tmpFilePath;
@property (nonatomic, copy) void (^completeBlock)(DYDownload *download);

@property (nonatomic, strong) NSMutableArray<DYDownloadEventAction *> *eventActions;

#pragma mark - Local
@property (nonatomic, assign) BOOL isZipFile;

@end

@implementation DYDownload

+ (DYDownload *)downloadFromPath:(NSString *)path {
    NSString *target = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:kDefaultDirectory];
    return [self downloadFromPath:path toPath:target];
}

+ (DYDownload *)downloadFromPath:(NSString *)path toPath:(NSString *)target {
    DYDownloadConfig *config = [DYDownloadConfig defaultConfig];
    config.path = path;
    config.target = target;
    return [self downloadWithConfig:config];
}

+ (DYDownload *)downloadWithConfig:(DYDownloadConfig *)config {
    if (config.path.length == 0) {
        return nil;
    }
    
    DYDownload *alreadyHere = [[DYDownloadSchedule sharedInstance] downloadForKey:config.key];
    if (alreadyHere) {
        return alreadyHere;
    }
    
    DYDownload *download = [[DYDownload alloc] init];
    download.config = config;
    if (config.target.length == 0) {
        NSString *target = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:kDefaultDirectory];
        download.config.target = target;
    }
    return download;
}

- (instancetype)init {
    if (self = [super init]) {
        _progress = [[NSProgress alloc] init];
    }
    return self;
}

- (DYDownload *)addTarget:(id)target action:(SEL)action forDownloadEvents:(DYDownloadEvents)downloadEvents {
    // 防止重复添加
    for (DYDownloadEventAction *ea in [self actionForEvent:downloadEvents]) {
        if ([ea.target isEqual:target] && sel_isEqual(action, ea.sel)) {
            return self;
        }
    }
    
    DYDownloadEventAction *ea = [[DYDownloadEventAction alloc] init];
    ea.event = downloadEvents;
    ea.target = target;
    ea.sel = action;
    [self.eventActions addObject:ea];
    return self;
}

- (void)removeTarget:(id)target action:(SEL)action forDownloadEvents:(DYDownloadEvents)downloadEvents {
    NSArray<DYDownloadEventAction *> *actions = [self actionForEvent:downloadEvents];
    for (NSInteger i = 0; i < actions.count; i++) {
        DYDownloadEventAction *ea = actions[i];
        if ([ea isEqual:target]) {
            if (action == NULL) {
                [self.eventActions removeObject:ea];
            } else if (sel_isEqual(action, ea.sel)) {
                [self.eventActions removeObject:ea];
            }
        }
    }
}

- (void)sendComplete:(void (^_Nullable)(DYDownload *))complete {
    self.completeBlock = complete;
    
    if (![self checkTargetDirectory]) {
        // 检查目标目录并创建
        !complete ?: complete(self);
        return;
    }
    
    if (![self checkVersion:self]) {
        // 版本检验没通过
        self.error = [NSError errorWithDomain:@"version check" code:DYDownloadErrorVersionLower userInfo:@{NSLocalizedDescriptionKey: @"本地文件版本比目标版本要大"}];
        self.state = DYDownloadStateFail;
        return;
    }
    
    [[DYDownloadSchedule sharedInstance] schedule:self];
}

- (void)suspend {
    [[DYDownloadSchedule sharedInstance] suspend:self];
}

- (void)resume {
    [[DYDownloadSchedule sharedInstance] resume:self];
}

- (void)cancel {
    [[DYDownloadSchedule sharedInstance] cancel:self];
}

- (void)setState:(DYDownloadStates)state {
    DYDownloadStates pre = _state;
    _state = state;
    
    DYDownloadEvents event = 0;
    switch (state) {
        case DYDownloadStateProcessing:
            if (pre != DYDownloadStateProcessing) {
                event = DYDownloadEventStart;
            } else {
                event = DYDownloadEventProcess;
            }
            break;
        case DYDownloadStateFail:
        case DYDownloadStateSuccess:
            event = DYDownloadEventDone;
            break;
        case DYDownloadStateSuspend:
            event = DYDownloadEventSuspend;
            break;
        case DYDownloadStateCanceld:
            event = DYDownloadEventCancel;
            break;
        default:
            break;
    }
    if (event == DYDownloadEventDone) {
        // 更新版本号
        [self updateVersion];
        // 解压无论成功与否，都将临时文件删除
        [self cleanTmpFile];
        // 结束后优先执行block
        !self.completeBlock ?: self.completeBlock(self);
    }
    [[self actionForEvent:event] enumerateObjectsUsingBlock:^(DYDownloadEventAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [obj invoke:self];
    }];
    
    if (state == DYDownloadStateFail && self.config.retryCount <= 0) {
        // 达到最大时清理
        [[DYDownloadSchedule sharedInstance] cleanDownload:self];
    }
    if (state == DYDownloadStateSuccess) {
        [[DYDownloadSchedule sharedInstance] cleanDownload:self];
    }
    [self handleLog];
}

- (void)setTmpFilePath:(NSURL *)tmpFilePath {
    _tmpFilePath = tmpFilePath;
    
    /// 收到中间文件
    if (![self checkTargetDirectory]) {
        return;
    }
    
    if (self.isZipFile) {
        [self unzipFile];
    } else {
        // 如果不需要解压缩
        NSError *error;
        NSString *fileName = self.config.targetName ?: [self.config.path lastPathComponent];
        if (![[NSFileManager defaultManager] moveItemAtPath:tmpFilePath.path toPath:[self.config.target stringByAppendingPathComponent:fileName] error:&error]) {
            self.error = error;
            self.state = DYDownloadStateFail;
        } else {
            self.state = DYDownloadStateSuccess;
        }
    }
}

- (NSURL *)tmpFileURL {
    return self.tmpFilePath;
}

- (void)dealloc {
    
}

#pragma mark - Version Controll
- (BOOL)checkTargetDirectory {
    BOOL isDir = NO;
    if (![[NSFileManager defaultManager] fileExistsAtPath:self.config.target isDirectory:&isDir]) {
        // 文件不存在，创建目标目录
        NSError *error;
        if (![[NSFileManager defaultManager] createDirectoryAtPath:self.config.target withIntermediateDirectories:YES attributes:nil error:&error]) {
            // 创建失败
            self.error = error;
            self.state = DYDownloadStateFail;
            return NO;
        }
    }
    return YES;
}

- (BOOL)checkMD5 {
    if (self.config.md5.length > 0) {
        NSString *md5 = [[NSString alloc] initWithData:[[NSData dataWithContentsOfURL:self.tmpFileURL] jk_md5Data] encoding:NSUTF8StringEncoding];
        if (![md5 isEqualToString:self.config.md5]) {
            self.error = [NSError errorWithDomain:@"Check MD5 Error" code:DYDownloadErrorMD5Error userInfo:@{NSLocalizedDescriptionKey : @"MD5校验失败"}];
            self.state = DYDownloadStateFail;
            return NO;
        }
    }
    return YES;
}

- (BOOL)checkVersion:(DYDownload *)download {
    if (download.config.versionKey.length <= 0) {
        // 如果没有配置，可以认为不考虑版本
        return YES;
    }
    
    if ([download.config.versionTarget compareVersion:download.config.versionLocal] == NSOrderedDescending) {
        return YES;
    }
    return NO;
}

- (void)updateVersion {
    DYDownloadVersionItem *item = [DYDownloadVersion.version itemForKey:self.config.versionKey];
    item.version = self.config.versionTarget;
    
    // 版本号对应的文件路径也一并加入
    NSString *fileName = [self.config.path lastPathComponent];
    NSString *path = [self.config.target stringByAppendingPathComponent:fileName];
    item.path = path;
    
    [DYDownloadVersion.version save];
}

- (void)setError:(NSError * _Nonnull)error {
    _error = error;
}

#pragma mark - Category Schedule

#pragma mark - Private
- (void)handleLog {
    if (self.state != DYDownloadStateFail) {
        // 只上报失败的日志
        return;
    }
    
    void (^logCallback)(NSString *) = self.config.logCallback ?: [DYDownloadConfig globalConfig].logCallback;
    if (!logCallback) {
        return;
    }
    
    NSMutableArray *mArr = [NSMutableArray array];
    [mArr addObject:self.config.path];
    NSString *errMsg = [NSString stringWithFormat:@"%@-%@", @(self.error.code), [self.error.userInfo objectForKey:NSLocalizedDescriptionKey]];
    [mArr addObject:errMsg];
    logCallback([mArr componentsJoinedByString:@","]);
}

- (void)cleanTmpFile {
    // 清理临时文件
    NSError *error;
    if (![[NSFileManager defaultManager] removeItemAtURL:self.tmpFilePath error:&error]) {
        
    }
}

- (void)unzipFile {
    __block BOOL bUnZip = YES;
    [[self actionForEvent:DYDownloadEventUnzip] enumerateObjectsUsingBlock:^(DYDownloadEventAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        // 有一个回调返回NO，中止解压
        bUnZip &= [obj invoke:self];
    }];
    if (!bUnZip) {
        self.error = [NSError errorWithDomain:@"unzip" code:DYDownloadErrorInterrupt userInfo:@{NSLocalizedDescriptionKey: @"解压被中止"}];
        self.state = DYDownloadStateFail;
        return;
    }
    
    // 异步解压
    __weak typeof(self) wSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSError *error;
        DYDownloadStates state = 0;
        if (![SSZipArchive unzipFileAtPath:self.tmpFilePath.absoluteString toDestination:self.config.target overwrite:NO password:nil error:&error]) {
            wSelf.error = error;
            state = DYDownloadStateFail;
        } else {
            state = DYDownloadStateSuccess;
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            wSelf.state = state;
        });
    });
}

- (NSMutableArray<DYDownloadEventAction *> *)eventActions {
    if (!_eventActions) {
        _eventActions = [NSMutableArray arrayWithCapacity:1];
    }
    return _eventActions;
}

- (NSArray<DYDownloadEventAction *> *)actionForEvent:(DYDownloadEvents)event {
    NSMutableArray *mArr = [NSMutableArray array];
    [self.eventActions enumerateObjectsUsingBlock:^(DYDownloadEventAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.event & event) {
            [mArr addObject:obj];
        }
    }];
    return mArr;
}

- (BOOL)isZipFile {
    return [self.config.path hasSuffix:@"zip"] && self.config.autoUnzip;
}
@end
